#include "MyGLWidget.h"
#include <GLES3/gl3.h>
#include <QRandomGenerator>

#define WIDTH 1920
#define HEIGHT 1080
#define IMAGE_WIDTH 200
#define IMAGE_HEIGHT 200

static const char *vertexShaderSource =
//      "precision highp float;\n"
    "#version 330\n"
    "attribute highp vec3 position;\n"
    "attribute mediump vec2 texCoord;\n"
    "varying mediump vec2 vTexCoord;\n"
    "void main(void)\n"
    "{\n"
    "   gl_Position = vec4(position, 1.0);\n"
    "   vTexCoord = texCoord;\n"
    "}\n";


static const char *fragmentShaderSource =
//    "precision highp float;\n"
    "#version 330\n"
    "varying mediump vec2 vTexCoord;\n"
    "uniform sampler2D texture;\n"
    "void main() {\n"
    "   gl_FragColor = texture2D(texture, vTexCoord);\n"
    "}\n";

void updatePixels(unsigned char* dst, int width, int height)
{
#if 0
    for (int i = 0; i < IMAGE_WIDTH * IMAGE_HEIGHT * 4; ++i) {
            dst[i] = QRandomGenerator::global()->bounded(256);
        }
#else
    static int color = 0;

    int* ptr = (int*)dst;

    // copy 4 bytes at once
    for(int i = 0; i < height; ++i)
    {
        for(int j = 0; j < width; ++j)
        {
            *ptr = color;
            ++ptr;
        }
        color += 257;
    }
    ++color;            // scroll down
#endif
}


MyGLWidget::MyGLWidget(QWidget *parent)
    : QOpenGLWidget(parent),
      fbo(nullptr),
      textureID(0), currentPBO(0), updateTimer(nullptr), rectangleSize(1.0f)
{
    // 启动定时器以触发动态内容更新
    updateTimer = new QTimer(this);
    connect(updateTimer, &QTimer::timeout, this, &MyGLWidget::updateDynamicContent);
    updateTimer->start(30); // 每秒更新一次
}

MyGLWidget::~MyGLWidget()
{
    delete fbo;
    glDeleteBuffers(2, pbo);
    glDeleteTextures(1, &textureID);
}

void MyGLWidget::initializeGL()
{
    initializeOpenGLFunctions();

#if 1
    // 创建 FBO
    init_fbo();
#endif
    init_fbo();
    // 创建 VBO 和 EBO
    init_vbo_and_ebo();
    // 创建并初始化纹理
    init_texture();
    // 创建双缓冲 PBO
    init_double_pbo();
    // 初始化当前 PBO
    currentPBO = 0;
    // 创建并编译着色器程序
    shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
    shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
    shaderProgram.link();
}

void MyGLWidget::resizeGL(int w, int h)
{
    glViewport(0, 0, w, h);
}

void MyGLWidget::paintGL()
{
    // 在 FBO 中渲染整个场景
//    renderToFBO();

    // 将 FBO 内容渲染到屏幕
    renderToScreen();
}

void MyGLWidget::setupVertexAttribs()
{
    // Bind VBO and EBO
    vbo.bind();
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    int positionLoc = shaderProgram.attributeLocation("position");
    shaderProgram.enableAttributeArray(positionLoc);
    shaderProgram.setAttributeBuffer(positionLoc, GL_FLOAT, 0, 3, 5 * sizeof(float));

    int texCoordLoc = shaderProgram.attributeLocation("texCoord");
    shaderProgram.enableAttributeArray(texCoordLoc);
    shaderProgram.setAttributeBuffer(texCoordLoc, GL_FLOAT, 3 * sizeof(float), 2, 5 * sizeof(float));
}

#if 1
void MyGLWidget::renderToFBO()
{
    fbo->bind();
    printf("fbo' width is %d, height is %d\n", fbo->width(), fbo->height());
    fflush(stdout);
    glViewport(0, 0, fbo->width(), fbo->height());
    glClearColor(0.0f, 0.5f, 0.5f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    shaderProgram.bind();
    setupVertexAttribs();

    // 绘制一个长方形
//    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

    shaderProgram.release();
    vbo.release();
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    fbo->release();
}

void MyGLWidget::renderToScreen()
{
    glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());
    glViewport(0, 0, fbo->width(), fbo->height());
    printf("render to screen: width is %d, height is %d\n", width(), height());
    fflush(stdout);
    glClearColor(0.0f, 0.5f, 0.5f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glBindTexture(GL_TEXTURE_2D, fbo->texture());
    shaderProgram.bind();
    setupVertexAttribs();

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

    vbo.release();
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    shaderProgram.release();
    fbo->release();
}
#endif

void MyGLWidget::updateDynamicContent()
{
#if 1
    // 更新 FBO 中的特定区域
#if 0
    int x = rand() % HEIGHT;  // 随机 x 坐标
    int y = rand() % 400;  // 随机 y 坐标
#else
    static int x = 0;
    static int y = 0;
    x+=200;
    y+=100;
    if (x >= WIDTH)
        x = 0;
    if (y >= HEIGHT)
        y = 0;
#endif
    printf("x is %d, y is %d\n", x, y);
    fflush(stdout);
//    int width = (int)(rectangleSize * WIDTH);  // 根据缩放因子计算宽度
//    int height = (int)(rectangleSize * HEIGHT); // 根据缩放因子计算高度
    int width = IMAGE_WIDTH;  // 根据缩放因子计算宽度
    int height = IMAGE_HEIGHT; // 根据缩放因子计算高度

    m_elapsed_timer.start();
    unsigned char *data = new unsigned char[width * height * 4];
    updatePixels(data, width, height);

    qint64 updatetime = m_elapsed_timer.elapsed();
    printf("update time is %d ms\n", updatetime);

    updatePartialArea(x, y, width, height, data);
    delete[] data;

    rectangleSize *= 0.95f;  // 每次更新时缩小 5%
#endif
    update();  // 触发重绘
}

void MyGLWidget::init_fbo()
{
    QOpenGLFramebufferObjectFormat format;
    format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
    fbo = new QOpenGLFramebufferObject(WIDTH, HEIGHT, format);
}

void MyGLWidget::init_vbo_and_ebo()
{
   GLfloat vertices[] = {
       // 位置      // 纹理坐标
       -1.0f, -1.0f, 1.0f, 0.0f, 0.0f,
        1.0f, -1.0f, 1.0f, 1.0f, 0.0f,
        1.0f,  1.0f, 1.0f, 1.0f, 1.0f,
       -1.0f,  1.0f, 1.0f, 0.0f, 1.0f
   };

   GLuint indices[] = {
       0, 1, 2,
       2, 3, 0
   };

   vbo.create();
   vbo.bind();
   vbo.allocate(vertices, sizeof(vertices));

   glGenBuffers(1, &ebo);
   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
   glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
}

void MyGLWidget::init_texture()
{
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, WIDTH, HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}

void MyGLWidget::init_double_pbo()
{
    glGenBuffers(2, pbo);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[0]);
    glBufferData(GL_PIXEL_UNPACK_BUFFER, WIDTH * HEIGHT * 4, nullptr, GL_STREAM_DRAW);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[1]);
    glBufferData(GL_PIXEL_UNPACK_BUFFER, WIDTH * HEIGHT * 4, nullptr, GL_STREAM_DRAW);
}

void MyGLWidget::updatePartialArea(int x, int y, int width, int height, const void *data)
{
    // 计算数据的大小
    int size = width * height * 4;
    printf("in %s, width is %d, height is %d\n", __FUNCTION__, width, height);

    m_elapsed_timer.start();
    // 绑定当前 PBO
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[currentPBO]);

    // 映射 PBO 到客户端内存
    void* ptr = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT);
    if (ptr) {
        // 计算数据偏移位置
        int offset = (y * WIDTH + x) * 4; // Assuming width of the framebuffer is WIDTH

        // 复制数据到映射的 PBO 内存区域
        memcpy(static_cast<unsigned char*>(ptr) + offset, data, size);

        // 取消映射
        glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
    }

    // 绑定 FBO
    fbo->bind();
    glBindTexture(GL_TEXTURE_2D, fbo->texture());

    // 使用 PBO 更新纹理
    glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    qint64 copytime = m_elapsed_timer.elapsed();
    printf("copy time is %d ms\n", copytime);

    // 解绑 FBO 和 PBO
    fbo->release();
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);

    // 切换 PBO
    currentPBO = (currentPBO + 1) % 2;
}

